﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Nvigorate.Common;
using Nvigorate.Common.Reflection;
using PostSharp.Extensibility;
using PostSharp.Laos;

namespace Nvigorate.Aspect
{
    /// <summary>
    /// Use this aspect on classes which have initialization type methods that must be called before
    /// calling anything else. This will check a property for a specific value and if that value does
    /// not have the expected value, the advice will call the specified prerequisite method for you.
    /// 
    /// Only use in rare circumstances when you need to deterministically call a function before any
    /// other code in a class executes. Even then, there are issues with this aspect as it currently
    /// exists.
    /// </summary>
    [MulticastAttributeUsage(MulticastTargets.Class, AllowMultiple=false)]
    public class PrerequisiteCallAspect : CompoundAspect
    {
        #region Private Fields

        private string _propertyCheck = "";
        private string _propertyValue = "";
        private string _requisisteCall = "";
        private string[] _requisiteValues = new string[] { };
        private List<string> _excludeList = new List<string>();

        #endregion

        #region Properties

        public string ExcludeList
        {
            get
            {
                return DelimitedBuilder.Construct(_excludeList, ",");
            }
            set
            {
                _excludeList = new List<string>(value.Split(','));
            }
        }

        public string PropertyCheck
        {
            get { return _propertyCheck; }
            set { _propertyCheck = value; }
        }

        public string PropertyValue
        {
            get { return _propertyValue; }
            set { _propertyValue = value; }
        }

        public string RequisisteCall
        {
            get { return _requisisteCall; }
            set { _requisisteCall = value; }
        }

        public string RequisiteValues
        {
            get
            {
                return DelimitedBuilder.Construct(_requisiteValues, ",");
            }
            set
            {
                _requisiteValues = value.Split(',');
            }
        }

        #endregion

        public override void ProvideAspects(object element, LaosReflectionAspectCollection collection)
        {
            Type targetType = element as Type;

            //Perform checks against the target type to ensure the following:
            // 1) The prerequisite property exists
            // 2) The prerequisite method call exists

            collection.AddAspect(targetType, new AddIPrerequisiteCallableAspect());

            if (Reflector.HasProperty(targetType, _propertyCheck) && Reflector.HasMethod(targetType, _requisisteCall))
            {
                foreach (MethodInfo method in targetType.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
                {
                    
                    if (!method.Name.Contains("get_") && 
                        !method.Name.Contains("set_") &&
                        method.Name != _requisisteCall &&
                        !_excludeList.Contains(method.Name) &&
                        !method.DeclaringType.Equals(typeof(object)))
                    {
                        Console.WriteLine("Applying Prerequisite aspect to " + method.Name);
                        collection.AddAspect(
                                                method, 
                                                new PrerequisiteAdvice(_propertyCheck, _propertyValue, _requisisteCall, _requisiteValues)
                                            );
                    }
                }
            }
        }

        [Serializable]
        internal class AddIPrerequisiteCallableAspect : CompositionAspect
        {
            public override Type GetPublicInterface(Type containerType)
            {
                return typeof(IPrerequisiteCallable);
            }

            public override object CreateImplementationObject(InstanceBoundLaosEventArgs eventArgs)
            {
                return new PrerequisiteCallable();
            }

            public override CompositionAspectOptions GetOptions()
            {
                return
                    CompositionAspectOptions.GenerateImplementationAccessor |
                    CompositionAspectOptions.IgnoreIfAlreadyImplemented;
            }
        }

        [Serializable]
        public class PrerequisiteAdvice : OnMethodBoundaryAspect
        {
            #region Private Fields

            private string _propertyCheck = "";
            private string _propertyValue = "";
            private string _requisisteCall = "";
            private string[] _requisiteValues = new string[] { };

            #endregion

            #region Properties

            public string PropertyCheck
            {
                get { return _propertyCheck; }
                set { _propertyCheck = value; }
            }

            public string PropertyValue
            {
                get { return _propertyValue; }
                set { _propertyValue = value; }
            }

            public string RequisisteCall
            {
                get { return _requisisteCall; }
                set { _requisisteCall = value; }
            }

            public string[] RequisiteValues
            {
                get { return _requisiteValues; }
                set { _requisiteValues = value; }
            }

            #endregion

            public override void OnEntry(MethodExecutionEventArgs eventArgs)
            {
                if (Reflector.Read(eventArgs.Instance, _propertyCheck).ToString() != _propertyValue)
                {
                    IPrerequisiteCallable target = eventArgs.Instance as IPrerequisiteCallable;
                    if(target != null)
                    {
                        if(target.PrerequisiteCallInProgress)
                            return;
                        else
                            target.PrerequisiteCallInProgress = true;
                    }
                    
                    if (_requisiteValues.Length == 0)
                        Reflector.CallMethod(eventArgs.Instance, _requisisteCall);
                    else
                        Reflector.CallMethod(eventArgs.Instance, _requisisteCall, _requisiteValues);

                    PropertyInfo property = eventArgs.Instance.GetType().GetProperty(_propertyCheck);
                    bool writable = (property == null) ? false : property.CanWrite;
                    if(writable)
                        Reflector.Write(eventArgs.Instance, _propertyCheck, _propertyValue);

                    if(target != null)
                        target.PrerequisiteCallInProgress = false;
                }

                base.OnEntry(eventArgs);
            }

            public PrerequisiteAdvice(string _propertyCheck, string _propertyValue, string _requisisteCall, string[] _requisiteValues)
            {
                this._propertyCheck = _propertyCheck;
                this._propertyValue = _propertyValue;
                this._requisisteCall = _requisisteCall;
                this._requisiteValues = _requisiteValues;
            }
        }

        public class PrerequisiteCallable : IPrerequisiteCallable
        {
            private bool _prerequisiteCallInProgress = false;

            #region IPrerequisiteCallable Members

            public bool PrerequisiteCallInProgress
            {
                get { return _prerequisiteCallInProgress; }
                set { _prerequisiteCallInProgress = value; }
            }

            #endregion
        }
        
        public PrerequisiteCallAspect(string _propertyCheck, string _propertyValue, string _requisisteCall)
        {
            this._propertyCheck = _propertyCheck;
            this._requisisteCall = _requisisteCall;
            this._propertyValue = _propertyValue;
        }
    }

    public interface IPrerequisiteCallable
    {
        bool PrerequisiteCallInProgress
        { get; set; }
    }
}
